#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

// int main() {
//   //  printf("hello worle\n");

//   // 在c语言程序当中 将会默认打开三个输入输出流 分别是
//   /*
//    * stdin
//    * stdout
//    * stderr
//    * 对应的在C++当中也会默认分打开三个标准输入输出流 分别为 cin  cout  cerr
//    */

//   /*
//     int open(const char *pathname, int
//     flags);//该版本一般用于打开已存在的文件
//
//     int open(const char *pathname,int flags,
//     mode_tmode);//该版本一般用于打开不存在的文件或者是创建一个新文件
//     其中mode即为权限 一般以八进制进行设置文件的权限

//     与fopen不同 上述的两个open函数为系统调用接口
//     对应的需要包含几个头文件 分别为
//     <sys/stat.h>
//     <sys/types.h>
//     <fcntl.h>

//     对应的在传参过程当中 该函数采用的传参方式是以比特位的传参形式进行传参
//     常用的几个选项分别为
//     O_RDONLY 只读
//     O_WRONLY 只写
//     O_RDWR 读写
//     O_CREAT 若是不存在则创建
//     O_TRUNC 清空
//     O_APPEND 追加

//     通常以|的形式进行传参 例: O_WRONLY|O_CREAT 即为以只读的方式打开
//     若是文件不存在则创建文件
//   */
//   return 0;
// }

// //--------------

// /* ###########比特位级别的标志传参方式###########*/

// #define ONE (1 << 0)    // 1
// #define TWO (1 << 1)    // 2
// #define THREE (1 << 2)  // 4
// #define FOUR (1 << 3)   // 8
// // 其中上述定义了四个掩码都是唯一的掩码当在进行与&的操作时将判断该位置上是否为1
// // 从而能够进行精准的传参
// void show(int flag) {
//   if (flag & ONE) {
//     printf("ONE\n");
//   }
//   if (flag & TWO) {
//     printf("TWO\n");
//   }
//   if (flag & THREE) {
//     printf("THREE\n");
//   }
//   if (flag & FOUR) {
//     printf("FOUR\n");
//   }
// }

// int main() {
//   show(ONE);
//   printf("-------------------------\n");
//   show(ONE | TWO);
//   printf("-------------------------\n");
//   show(TWO | THREE);
//   printf("-------------------------\n");
//   show(ONE | FOUR);
//   printf("-------------------------\n");
//   return 0;
// }
// //而在open函数当中的flag参数则是以该种方式进行判断从而进行多种操作

// //--------------

// int main() {
//   // 以读的形式打开文件
//   int fd = open("myfile_1.txt",O_WRONLY);//运行的时候无反应
//   // 因为在当前目录下不存在该文件

//   close(fd);  // 关闭文件
//   return 0;
// }

// // //========================================================================

// int main() {

//   // 以只读的形式打开文件 若是文件不存在则创建文件
//   int fd = open("myfile_1.txt", O_WRONLY | O_CREAT);//注意判断fd是否<0
//   若是<0则打开失败
//   // 运行后在终端中ls 出现了myfile_1.txt文件 但是文件的权限为乱码
//   /*
//     -rw-rw-r-- 1 _LJP _LJP    73 Apr 29 15:18 makefile
//     --ws--x--- 1 _LJP _LJP     0 Apr 29 16:44 myfile_1.txt --> 乱码权限
//     -rwxrwxr-x 1 _LJP _LJP 11000 Apr 29 16:44 mytest
//     -rw-rw-r-- 1 _LJP _LJP  2779 Apr 29 16:44 test.c
//   */

//   close(fd);  // 关闭文件
//   return 0;
// }

// //========================================================================

// int main() {
//   // // 以只读的形式打开文件 若是文件不存在则创建文件
//   // int fd = open("myfile_2.txt", O_WRONLY | O_CREAT, 0666);
//   // 注意判断fd是否<0 若是<0则打开失败
//   // // 以八进制的方式 设置第三个参数为0666作为创建文件时的文件权限

//   // /*
//   //   -rw-rw-r-- 1 _LJP _LJP     0 Apr 29 16:49 myfile_2.txt
//   //   确认文件被创建且权限不乱码
//   //   但是文件的权限少了2位实际上是环境当中的umask码为2 而文件的权限为
//   实际权限
//   //   = 默认权限 & (~umask)

//   //   $ umask
//   //     0002

// //----- ----- ----- ----- ----- -----

//   //     为了避免这种情况可以在文件当中直接调用umask() 函数来清空umask码

//   // */

//   umask(0);//umask接口将会影响当前进程 对于程序执行后而言
//   即为程序执行后的进程 int fd = open("myfile_3.txt", O_WRONLY | O_CREAT,
//   0666); if(fd<0){
//     printf("open file error\n");
//     return -1;
//   }
//   /*
//     -rw-rw-r-- 1 _LJP _LJP     0 Apr 29 16:49 myfile_2.txt
//     -rw-rw-rw- 1 _LJP _LJP     0 Apr 29 16:59 myfile_3.txt
//     文件创建成功 且文件的权限不受系统的umask掩码影响
//     (已在当前进程当中重新设置umask码为0)
//   */

//   close(fd);  // 关闭文件
//   return 0;
// }

// //========================================================================

// int main(){
//   //在上述的代码当中已经分别创建了三个myfile.txt文件
//   //根据上述的myfile.txt文件从而继续完善对应的读写操作
//   int fd = open("myfile_3.txt", O_WRONLY | O_CREAT);

//   if(fd<0){
//     printf("open file error\n");
//     return 1;
//     // 在打开文件时判断打开文件是否失败
//     若是返回值文件描述符fd<0时则表示打开失败
//     // 由于在上面的代码当中已经创建了对应的文件 故以
//     /* int open(const char *pathname, int flags); */
//     //作为该段测试的使用接口
//   }

//   // char* message = "xxxxxxxxxxxxxxxxxxxxxxxx\n";
//   char* message = "aaa";

//   write(fd,message,strlen(message));  // 该接口为系统调用的一个接口
//   具体可以将内容写入至文件
//   // 在该段代码当中 从开头对文件进行写入(如果从头开始具有其他内容将会被覆盖)
//   且不清空文件
//   // 不清空文件的具体表象为文件的打开方式
//   //
//   在该段代码当中文件的打开方式利用系统调用接口并以二进制位传参的方式进行传参从而能够达到一种多选项的机制
//   // 具体实现参考上述代码;

//   //而实际上若是需要完成fopen(w) 或是
//   fopen(a)的功能则需根据需求来更改是否清空文件或者追加内容 即为

//   /*
//     int fd = open("myfile_3.txt", O_WRONLY | O_CREAT | O_TRUNC ); -- fopen(w)
//     若是不存在文件则创建 若是存在文件则清空 int fd = open("myfile_3.txt",
//     O_WRONLY | O_CREAT | O_APPEND ); -- fopen(a) 若是不存在文件则创建
//     若是存在文件则在当前文件下继续追加内容
//   */

//   close(fd);
//   return 0;
// }

/*
  在底层当中 以FILE* fp =
  fopen("log.txt","w");为例的代码实际上是去调用了对应的系统调用接口
  在上文当中提到了对应的系统调用接口存在一个open()的接口
  而对于上述的代码为例其一定是封装了下列的代码:
  int fd = open("log.txt" , O_WRONLY | O_CREAT | O_TRUNC );
  即当一个文件不存在时则去新建这个文件 若是这个文件存在时则去清空这个文件;
  在代码的参数当中,可以根据判断或者条件判断等方式来确定对应的打开文件的方式;
  而在上面的代码例子当中存在一个类型为FILE*的指针作为返回值,而在系统调用接口当中的返回值类型为一个int整形类型;

  实际上这个系统调用接口的int类型的返回值即为fd文件描述符;

*/

// //========================================================================

// 在Linux操作系统当中存在一句话即为 Linux当中一切皆文件
/*
    对于外设也是如此;
    从上层至下层的顺序一般为:
      用户 应用程序 系统调用接口  操作系统 硬件驱动 硬件
    每一个底层都在为其对应的上层进行服务
    以用户的视角来看实际上用户是无法直接绕过操作系统直接去访问硬件
    而实际上一般只能通过贯穿的形式访问到对应的硬件

    当然对应的操作系统也要去管理对应的硬件并且为用户提供对应的系统调用接口

    在Linux内核当中 当一个文件打开时将会对应的为该文件创建一个file结构体
    这个file结构体当中将会储存大量的和该文件有关的信息,这包括:
        文件在磁盘的位置
        文件的基本属性 大小 读写位置和打开者
        文件内核缓冲区信息
        对应的struct_file*next/prev指针
        引用计数(判断引用计数是否为0
   从而判断该struct_file结构体对象是否需要被回收)

   //---

   而在之前提到过 对于操作系统而言 当一个程序被打开时将会以一个进程的形式存在
   对应的实际上与文件关系较为密切的关联着实际上即为进程 进程打开文件
   同时 进程与文件的关系是一比多的 即一个进程可以同时打开一堆文件
   在前文当中提到
   进程在被打开时由于OS需要进行管理(先描述后组织),故将会为该进程创建一个对应的进程控制块(task_struct)
   而在进程控制块这个结构体当中存在着一个结构体成员"struct file_struct*files"
   指针;
   这个指针指向一个struct file_struct的数据结构
   而该数据结构当中存在着一个数组为( 'struct file* fd_array[]' 数组)
   对应的这块数组当中每个空间都存在着一个指针 且每个指针指向对应的打开文件的实例
   这里的指针在这里被称作文件流 而对应的数组下的下表被称为文件描述符fd ↓↓↓

*/

// int main() {
  // //
  // 在上文当中提到了当打开一个文件时对应的int类型的返回值fd即为其的文件描述符;

  // int fd1 = open("myfile_1.txt", O_WRONLY | O_CREAT);
  // int fd2 = open("myfile_2.txt", O_WRONLY | O_CREAT);
  // int fd3 = open("myfile_3.txt", O_WRONLY | O_CREAT);

  // printf("fd1 : %d\n", fd1);
  // printf("fd2 : %d\n", fd2);
  // printf("fd3 : %d\n", fd3);
  // printf("###############################\n");

  /*
    $ ./mytest
    fd1 : 3
    fd2 : 4
    fd3 : 5
  */
  // 对应的运行的结果为3 4 5

  /*
    且在上文当中提到一个概念 在操作系统当中,默认会打开三个输入输出流
    而在这里可以看到打开的文件对应的返回的文件描述符是从3开始的
    意思就是在文件描述符表中 下表为0,1,2已经被占用 而实际上这里的0,1,2分别即为
    标准输入'stdin' 标准输出'stdout' 标准错误'stderr'
    且三个文件流对应的硬件分别为 键盘 显示屏 显示屏
    可以以write close read 三个接口分别进行实验
  */

  // // 1. 利用read接口与write接口进行实验
  // /*
  //   使用read接口且利用文件描述符0进行输入
  //   且使用write接口利用文件描述符1,2输出至显示屏当中
  // */
  // char buff[1024];
  // buff[1024] = 0;
  // ssize_t ret = read(0, buff, sizeof(buff));
  // //read 函数的返回值 若是返回一个非负整数则表示 read函数调用成功
  // 且对应的返回值为一个读取到的内容的字节数
  // //若是返回一个负数 则表示函数调用失败
  // if (ret > 0) {
  //   buff[ret] = '\0';
  //   write(1, buff, strlen(buff));
  //   write(2, buff, strlen(buff));
  // }
  // /*
  //   程序执行后的结果如下
  //     $ ./mytest
  //       asfafaf
  //       asfafaf
  //       asfafaf
  // */

  //----------------

  // 2. 利用close函数与printf perror函数进行实验
  /*
    利用colos函数关闭对应的文件描述符1的文件流 且分别用printf perror函数进行证明
  */

  // close(1);
  // printf("printf222\n");
  // perror("perror111\n");

  /*
  $ ./mytest
  perror111
  : Bad file descriptor
  打印结果如上 对应的标准输出流被关闭无法正常输出 同时perror 提示无效文件
  而标准错误流仍可以打印对应的信息
  */

  //---

  // close(2);
  // printf("printf222\n");
  // perror("perror111\n");
  /*
    $ ./mytest
    printf222
    而在这个例子当中 文件描述符2被关闭 同时对应的标准错误并未被打印与触发
 
 //也可使用fprintf来调用 stdout 和 stderr 来进行测试
 
  */



  // 为什么当一个文件描述符被关闭时操作系统同不会对应的去回收其信息 ?
  /*
     在上述的例子当中见到了一个神奇的现象
     即为明明使用了close接口关闭对应的文件流 但对应的还是可以使用该硬件(文件)
     在上文中提到 Linux当中一切皆文件 从某种意义上来说硬件也是一种文件
     而文件将会有对应的file结构体 这个结构体中存储了一个很重要的成员(引用计数)
     即通过引用计数来判断这个资源是否还有被哪个进程使用 当调用了对应的open函数时
     对应的引用计数将会+1 对应的当调用了对应的close函数时 对应的引用计数将会-1
     当引用计数变为0值时 则表示该资源需要被进行回收
  */

  ////////////            回归刚才的问题           ////////////
  ////////////  对于FILE*指针与文件描述符int的关系  ////////////

  /*
      实际上 FILE 类型实际上是c语言封装出来的一种结构体类型
      而其要进行对应的文件操作那么必定需要调用操作系统的系统调用接口
      那么必定需要使用文件描述符fd
      可以推断出实际上在FILE这个类型的定义当中
  主要的核心一定是依靠文件描述符fd进行的
  // 那么如何进行证明
  */

  // 证明:
  // printf("stdin->fd:%d\n", stdin->_fileno);
  // // 对应的是去封装的操作系统的数据以及接口
  // // 而输入输出错误流的文件描述符在结构体变量当中被声明为_fileno
  // printf("stdout->fd:%d\n", stdout->_fileno);
  // printf("stderr->fd:%d\n", stderr->_fileno);
  /*
  代码的运行结果如下
  $ ./mytest
  stdin->fd:0
  stdout->fd:1
  stderr->fd:2
  */

  // 那么为什么操作系统在默认的时候将会打开这三个标准输入输出流
  /*
     在OS当中 操作系统为用户提供了一个抽象层的接口
     而这个接口就是对应的标准输入输出流
     同时这个对应的标准输入输出流由于是一个抽象层的接口所以并不需要用户进行思考
     只需无脑使用即可 同时在OS的内核当中
     这三个标准输入输出流是被设置成默认打开的状态
     本身与当前的硬件设置没有太大关系


     在OS的启动当中 OS将默认去检测当前的硬件 这包括CPU GPU 内存 输入输出设备等
     以保证这些硬件功能的良好 同时判断自己可以使用哪些硬件
     而这个检测出来的硬件本身与三个标准输入输出流无关
     因为无论是否有检测到输入输出设备 都会打开 即使没有检测到输入输出设备
     也可以通过其他的方式进行输入输出 所以本身三个标准的输入输出流的打开的状态

  */

//   return 0;
// }
